import {
  expectedHeaderNames,
  expectedNGConstants,
  kPendingRequestCalls,
  kUpdateTimer,
  HTTP2_METHOD_GET,
  HTTP2_METHOD_CONNECT,
  kAuthority,
  kProtocol,
  kSentHeaders,
  kOrigin,
  kState,
  STREAM_FLAGS_HAS_TRAILERS,
  STREAM_OPTION_EMPTY_PAYLOAD,
  STREAM_OPTION_GET_TRAILERS,
  HTTPSTREAM_CALLBACK_TYPE_DATA_TRUNCK,
  HTTPSTREAM_CALLBACK_TYPE_REPONSE,
  HTTP2_HEADER_AUTHORITY,
  HTTP2_HEADER_PATH,
  HTTPSTREAM_CALLBACK_TYPE_CLOSE,
  HTTPSTREAM_CALLBACK_TYPE_END,
  HTTPSTREAM_CALLBACK_TYPE_ERROR,
  ERR_CODE,
  kSession,
  HTTPSTREAM_CALLBACK_TYPE_GOWAY,
  HTTP2_HEADER_METHOD,
  constants
} from "../constant/Constant";

import {
  assertIsObject,
  assertValidPseudoHeader,
  checkIsHttpToken,
  debugSessionObj,
  getAuthority,
  isNetWorkAvailable,
  isPayloadMeaningless,
  mapToHeaders,
  setAndValidatePriorityOptions,
  socketOnError,
  validateBoolean
} from "../utils";
import { hilog } from "@kit.PerformanceAnalysisKit";

import { ClientHttp2Stream } from '../stream/ClientHttp2Stream'
import { ClientSessionOptions } from "../typeDeclaration";
import { Http2Session } from "./Http2Session";
import { incomingHttpHeaders, IncomingHttpStatusHeader } from '../typeDeclaration'
import buffer from "@ohos.buffer";
import ManagerMap from "../ManagerMap";
import util from "@ohos.util";
import {LogUtils} from "../LogUtils";

export class ClientHttp2Session extends Http2Session {
  _socket: Object;
  _options: Object;

  constructor(options, socket) {
    super(expectedNGConstants.NGHTTP2_SESSION_CLIENT, options, socket);
    this._socket = socket;
    this._options = options;
    this[kPendingRequestCalls] = null;
  }

  removeAllListeners() {
  };

  close() {
  };

  // Submits a new HTTP2 request to the connected peer. Returns the
  // associated Http2Stream instance.
  request(headers?, options?) {
    //TODO this.destroyed will crash
    if (this.destroyed()) {
      throw new Error('ERR_HTTP2_INVALID_SESSION');
    }

    if (this.closed) {
      throw new Error('ERR_HTTP2_GOAWAY_SESSION');
    }

    this[kUpdateTimer]();

    if (headers !== null && headers !== undefined) {
      const keys = Object.keys(headers);
      for (let i = 0; i < keys.length; i++) {
        const header = keys[i];
        if (header[0] === ':') {
          assertValidPseudoHeader(header);
        } else if (header && !checkIsHttpToken(header)) {
          this.closeSession(this, "ERR_INVALID_HTTP_TOKEN Header name", ERR_CODE.ERR_INVALID_HTTP_TOKEN)
        }
      }
    }

    assertIsObject(headers, 'headers');
    assertIsObject(options, 'options');

    options = { ...options };

    if (headers[expectedHeaderNames.HTTP2_HEADER_METHOD] === undefined) {
      headers[expectedHeaderNames.HTTP2_HEADER_METHOD] = HTTP2_METHOD_GET;
    }

    const connect = headers[expectedHeaderNames.HTTP2_HEADER_METHOD] === HTTP2_METHOD_CONNECT;

    if (!connect || headers[expectedHeaderNames.HTTP2_HEADER_PROTOCOL] !== undefined) {
      if (getAuthority(headers) === undefined) {
        headers[expectedHeaderNames.HTTP2_HEADER_AUTHORITY] = this[kAuthority];
      }
      if (headers[expectedHeaderNames.HTTP2_HEADER_SCHEME] === undefined) {
        headers[expectedHeaderNames.HTTP2_HEADER_SCHEME] = this[kProtocol].slice(0, -1);
      }
      if (headers[expectedHeaderNames.HTTP2_HEADER_PATH] === undefined) {
        headers[expectedHeaderNames.HTTP2_HEADER_PATH] = '/';
      }
    } else {
      if (headers[expectedHeaderNames.HTTP2_HEADER_AUTHORITY] === undefined) {
        LogUtils.getInstance().error('ERR_HTTP2_CONNECT_PATH')
        throw new Error('ERR_HTTP2_CONNECT_AUTHORITY');
      }
      if (headers[expectedHeaderNames.HTTP2_HEADER_SCHEME] !== undefined) {
        LogUtils.getInstance().error('ERR_HTTP2_CONNECT_PATH')
        throw new Error('ERR_HTTP2_CONNECT_SCHEME');
      }
      if (headers[expectedHeaderNames.HTTP2_HEADER_PATH] !== undefined) {
        LogUtils.getInstance().error('ERR_HTTP2_CONNECT_PATH')
        throw new Error('ERR_HTTP2_CONNECT_PATH');
      }

    }

    setAndValidatePriorityOptions(options);

    if (options.endStream === undefined) {
      options.endStream = isPayloadMeaningless(headers[expectedHeaderNames.HTTP2_HEADER_METHOD]);
    } else {
      validateBoolean(options.endStream, 'options.endStream');
    }

    const headersList = mapToHeaders(headers);

    // eslint-disable-next-line no-use-before-define
    // let opt = this._options as ClientSessionOptions;
    let reqId = util.generateRandomUUID(true);
    const stream = new ClientHttp2Stream(this, this._socket, reqId, {});
    stream[kSentHeaders] = headers;
    stream[kOrigin] = `${headers[expectedHeaderNames.HTTP2_HEADER_SCHEME]}://` +
      `${getAuthority(headers)}`;
    if (options.endStream) {
      stream.end();
    }

    if (options.waitForTrailers) {
      stream[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;
    }
    this.requestOnConnect(headersList, this._socket, reqId, stream)
    if (this[kPendingRequestCalls] != null) {
      this.once('connect', () => {
        if (this[kPendingRequestCalls] != null) {
          this[kPendingRequestCalls].forEach((f) => f());
          this[kPendingRequestCalls] = null;
        }
      });
    }
    return stream;
  }


  requestOnConnect(headers: any, handle: any, reqId: any, stream: any) {
    const session = stream[kSession];
    const responseCallBack = (reqId: string, type: number, data: any, rpcType:number) => {
      let uuid = reqId;
      LogUtils.getInstance().info("responseCallBack  type " + type + " reqId " + uuid +" rpcType "+ rpcType);
      if (type == HTTPSTREAM_CALLBACK_TYPE_DATA_TRUNCK) {
        let callback = ManagerMap.getInstance().onCallBackMap.get(uuid + "data")
        if (callback) {
          callback(data);
        }
        if(rpcType == 1) {
          try {
            ManagerMap.getInstance().onCallBackMap.delete(uuid + "data");
            ManagerMap.getInstance().onCallBackMap.delete(uuid + "response");
            ManagerMap.getInstance().onCallBackMap.delete(uuid + "error");
            ManagerMap.getInstance().onCallBackMap.delete(uuid + "close");
            ManagerMap.getInstance().onCallBackMap.delete(uuid + "end");
          } catch (e) {
            LogUtils.getInstance().error("Error onCallBackMap delete reqId " + uuid)
          }
        }
      }
      if (type == HTTPSTREAM_CALLBACK_TYPE_REPONSE) {
        try {
          let callback = ManagerMap.getInstance().onCallBackMap.get(uuid + "response")
          let headers = JSON.parse(data) as incomingHttpHeaders & IncomingHttpStatusHeader
          if (callback) {
            callback(headers, 4);
          }
        }catch (e) {
          LogUtils.getInstance().error("Error parse response failed reqId " + uuid)
        }
      }
      if (type == HTTPSTREAM_CALLBACK_TYPE_CLOSE) {
        let callback = ManagerMap.getInstance().onCallBackMap.get(uuid + "close")
        stream.rstCode = new Number(data).valueOf();
        if (callback) {
          callback();
        }
        session.nextTickEmit('close', { data: [{ "rstCode": new Number(data).valueOf() }] })
      }
      if (type == HTTPSTREAM_CALLBACK_TYPE_END) {
        let callback = ManagerMap.getInstance().onCallBackMap.get(uuid + "end")
        if (callback) {
          callback();
        }
      }
      if (type == HTTPSTREAM_CALLBACK_TYPE_ERROR) {
        let callback = ManagerMap.getInstance().onCallBackMap.get(uuid + "error")
        if (callback) {
          callback(data);
        }
        session.nextTickEmit('error', { data: [{ "message": data }] })
      }
    }
    LogUtils.getInstance().info("request reqId: " + reqId + " headers: " + JSON.stringify(headers))
    if (isNetWorkAvailable() && handle) {
      try {
        handle.request(reqId, headers[HTTP2_HEADER_AUTHORITY], headers[HTTP2_HEADER_PATH], JSON.stringify(headers),
          responseCallBack).then(() => {
          Object.keys(headers).forEach(key => {
            delete headers[key];
          });
        })
      }catch (e) {
        LogUtils.getInstance().error("Error handle request");
      }
    } else {
      session.nextTickEmit('error', { data: [{ "message": "Error Currently no network" }] })
      throw ("Currently no network")
    }
  }
}